/*
 * Copyright (c) 2018 Hitevision
 */

package tw.com.hitevision.whiteboard.android.storage;

import android.Manifest;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import tw.com.hitevision.whiteboard.android.Props;
import tw.com.hitevision.whiteboard.android.util.GlideApp;

public class FileHelper {
    private static final String TAG = FileHelper.class.getSimpleName();
    public static final String FOLDER_SCREENSHOTS_19C = "/MeetingMinutes";
    public static final String FOLDER_SCREENSHOTS = "/Pictures/Screenshots";
    public static final String FOLDER_IMAGE = "/image";
    private static final String FOLDER_THUMBNAILS = "/data/media/0/Whiteboard/icon";
    private static final String FOLDER_THUMBNAILS_ALT = "/Whiteboard/icon";
    private static final String FOLDER_PRIVATE_BACKGROUND = "Background";
    private static final String FOLDER_PRIVATE_BACKGROUND_NAME = "background";
    public static final String FILE_PATH = "filePath";

    private static final int THUMBNAIL_SAMPLE_SIZE = 8;

    private static class FileHelperHolder {
        private static FileHelper instance;
        private static void createFileHelper(Context context) {
            instance = new FileHelper(context);
        }
    }

    public static synchronized FileHelper getInstance(Context context) {
        if (FileHelperHolder.instance == null) {
            FileHelperHolder.createFileHelper(context);
        }
        return FileHelperHolder.instance;
    }

    private final Context context;
    private final DateFormat imageDateFormat;
    private File imageFolder;
    private File thumbnailFolder;

    private FileHelper(Context context) {
        this.context = context.getApplicationContext();
        imageDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US);
        if(Props.getFlavorChipset().equals(Props.Flavor.Chipset.Hisi)){
            imageFolder = new File(Environment.getExternalStorageDirectory().getPath(), FOLDER_SCREENSHOTS_19C + FOLDER_IMAGE);
        }else{
            imageFolder = new File(Environment.getExternalStorageDirectory().getPath(), FOLDER_SCREENSHOTS + FOLDER_IMAGE);
        }

        if (!imageFolder.exists() && hasStoragePermission()) {
            if (!imageFolder.mkdirs()) {
                imageFolder = Environment.getExternalStorageDirectory();
            }
        }

        // the following announce was avoiding thumbnail only for navigatorView somehow would be detected by system ( file commander )
        // and will cause the END MEETING couldn't get img from WhiteBoard
        // but maybe the system fixed the load img part
//        thumbnailFolder = new File(FOLDER_THUMBNAILS);
        ContextWrapper cw = new ContextWrapper(context);
        thumbnailFolder = new File(cw.getFilesDir().getPath() + FOLDER_THUMBNAILS_ALT);

        if (!thumbnailFolder.exists() && hasStoragePermission()) {
            if (!thumbnailFolder.mkdirs()) {
                thumbnailFolder = Environment.getExternalStorageDirectory();
            }
        }
    }

    private boolean hasStoragePermission() {
        return ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
    }

    public String generateFileName(boolean isAnnotationState) {
        final String timestamp = imageDateFormat.format(new Date());
        String name = String.format(Locale.US, isAnnotationState ? "AN%s.png" : "WB%s.png", timestamp);
        File file = new File(imageFolder, name);

        int counter = 0;
        while (file.exists()) {
            counter++;
            name = String.format(Locale.US, isAnnotationState ? "AN%s (%d).png" : "WB%s (%d).png", timestamp, counter);
            file = new File(imageFolder, name);
        }

        if (hasStoragePermission()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return file.getName();
    }

    public File generateCacheFile(Context context) {
        final String timestamp = imageDateFormat.format(new Date());
        return generateUniqueFile(String.format("%s.png", timestamp), context.getCacheDir().toString(), null);
    }

    private File generateUniqueFile(String fileName, String mountPath, String mountFolder) {
        final File folder;
        if (mountFolder != null) {
            folder = new File(mountPath, mountFolder);
        } else {
            folder = new File(mountPath);
        }

        if (!folder.exists() && hasStoragePermission()) {
            folder.mkdirs();
        }

        File file = new File(folder, fileName);
        if (file.exists()) {
            final int period = fileName.lastIndexOf('.');
            final String ext = fileName.substring(period + 1);
            final String originalName = fileName.substring(0, period);

            int counter = 0;
            while (file.exists()) {
                counter++;
                String name = String.format(Locale.US, "%s (%d).%s", originalName,
                        counter, ext);
                file = new File(folder, name);
            }
        }
        return file;
    }

    public File getPreviewFile(String fileName) {
        return new File(imageFolder, fileName);
    }

    public File getThumbnailFile(String fileName) {
        return thumbnailFolder != null ? new File(thumbnailFolder, fileName) : getPreviewFile(fileName);
    }

    private final FileFilter annotationScreenshotsFilter = new FileFilter() {
        @Override
        public boolean accept(File pathname) {
            return pathname.getName().matches("AN\\d+( \\(\\d+\\))?.png");
        }
    };

    /**
     * Returns a list of files for annotation screenshots.
     */
    public List<File> getAnnotationScreenshots() {
        return Arrays.asList(imageFolder.listFiles(annotationScreenshotsFilter));
    }

    void writeToCache(Context context, String fileName, Bitmap image) {
        if (!hasStoragePermission()) {
            return;
        }

        final File file = new File(context.getCacheDir(), fileName);

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // write to file
        try {
            FileOutputStream outputStream = new FileOutputStream(file);
            image.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void copyPreviewToFile(Context context, String fileName, String mountPath, String targetFileName) {
        if (!hasStoragePermission()) {
            return;
        }

        File preview = new File(imageFolder, fileName);
        if (!preview.exists()) {
            return;
        }

        if (mountPath == null) {
            mountPath = imageFolder.getPath();
        }

        final File file;
        if (targetFileName == null) {
            file = generateUniqueFile(fileName, mountPath, Props.getMountFolder());
        } else {
            file = new File(mountPath, targetFileName);
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        copy(preview, file);

        // notify media scanner
        MediaScannerConnection.scanFile(context, new String[] {file.getPath()}, null, new MediaScannerConnection.OnScanCompletedListener() {
            @Override
            public void onScanCompleted(String path, Uri uri) {
                syncFile(context, uri);
            }
        });

        if (thumbnailFolder != null && mountPath.equals(imageFolder.getPath())) {
            // create a thumbnail
            saveSmallToFile(preview, new File(thumbnailFolder, targetFileName));
        }
    }

    private void syncFile(Context context, Uri uri) {
        try (ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r")) {
            FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
            fileDescriptor.sync();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void copy(File src, File dst) {
        if (!hasStoragePermission()) {
            return;
        }

        try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) {
            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void saveToFile(Context context, Bitmap image, String fileName, @Nullable String mountPath) {
        if (!hasStoragePermission()) {
            return;
        }

        File file;
        if (mountPath != null) {
            file = generateUniqueFile(fileName, mountPath, Props.getMountFolder());
        } else {
            file = new File(imageFolder, fileName);
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // remove stale thumbnail
        removeImageThumbnail(context, file.getPath());

        // write to file
        try (FileOutputStream out = new FileOutputStream(file)) {
            image.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (thumbnailFolder != null && mountPath == null) {
            // create a thumbnail
            saveSmallToFile(image, new File(thumbnailFolder, fileName));
        }

        // notify media scanner
        MediaScannerConnection.scanFile(context, new String[] {file.getPath()}, null, null);
    }

    /**
     * Deletes an image thumbnail from the Media Store.
     */
    private void removeImageThumbnail(Context context, String imageFile) {
        if (!hasStoragePermission()) {
            return;
        }

        Cursor cursor = context.getContentResolver().query(
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] {MediaStore.Images.Media._ID},
                MediaStore.Images.Media.DATA + "=? ", new String[] {imageFile}, null);
        int imageId = -1;
        if (cursor != null && cursor.moveToFirst()) {
            imageId = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            cursor.close();
        }

        if (imageId != -1) {
            final ContentResolver resolver = context.getContentResolver();
            Cursor thumbnails = resolver.query(android.provider.MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, null, android.provider.MediaStore.Images.Thumbnails.IMAGE_ID + "=?", new String[] {String.valueOf(imageId)}, null);
            for (thumbnails.moveToFirst(); !thumbnails.isAfterLast(); thumbnails.moveToNext()) {
                long thumbnailId = thumbnails.getLong(thumbnails.getColumnIndex(android.provider.MediaStore.Images.Thumbnails._ID));
                String path = thumbnails.getString(thumbnails.getColumnIndex(android.provider.MediaStore.Images.Thumbnails.DATA));
                File file = new File(path);
                if (file.delete()) {
                    resolver.delete(android.provider.MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, android.provider.MediaStore.Images.Thumbnails._ID + "=?", new String[] {String.valueOf(thumbnailId)});
                }
            }
            thumbnails.close();
        }
    }

    private void saveSmallToFile(Bitmap image, File file) {
        if (!hasStoragePermission()) {
            return;
        }

        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }

        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = THUMBNAIL_SAMPLE_SIZE;
        options.inPreferredConfig = Bitmap.Config.RGB_565;

        Bitmap bitmap = null;
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            image.compress(Bitmap.CompressFormat.JPEG, 50, baos);

            try (ByteArrayInputStream in = new ByteArrayInputStream(baos.toByteArray())) {
                bitmap = BitmapFactory.decodeStream(in, null, options);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (bitmap != null) {
            try (FileOutputStream out = new FileOutputStream(file)) {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
                out.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void saveSmallToFile(File originalFile, File targetFile) {
        if (!hasStoragePermission()) {
            return;
        }

        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }

        if (!targetFile.exists()) {
            try {
                targetFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        try (FileInputStream fileIn = new FileInputStream(originalFile)) {
            Bitmap image = BitmapFactory.decodeStream(fileIn, null, null);
            if (image != null) {
                saveSmallToFile(image, targetFile);
                image.recycle();
            } else {
                copy(originalFile, targetFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    void delete(Context context, String fileName) {
        if (!hasStoragePermission()) {
            return;
        }

        final File image = new File(imageFolder, fileName);
        if (image.exists()) {
            try {
                image.delete();

                // notify media scanner
//            context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(image)));
                MediaScannerConnection.scanFile(context, new String[] {image.getPath()}, null, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (thumbnailFolder != null) {
            final File thumbnail = new File(thumbnailFolder, fileName);
            if (thumbnail.exists()) {
                try {
                    thumbnail.delete();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // https://stackoverflow.com/questions/17674634/saving-and-reading-bitmaps-images-from-internal-memory-in-android
    public void saveFileToInternalStorage(Bitmap bitmap) {
        ContextWrapper cw = new ContextWrapper(context);
        // path to /data/data/yourapp/app_data/imageDir
        File directory = cw.getDir(FOLDER_PRIVATE_BACKGROUND, Context.MODE_PRIVATE);
        // Create imageDir
        File imageDir = new File(directory, FOLDER_PRIVATE_BACKGROUND_NAME + ".png");

        try {
            // try to recycle old bitmap
            Bitmap oldBitmap = getCustomizedBackground();
            if (oldBitmap != null) {
                GlideApp.get(context)
                        .getBitmapPool()
                        .put(oldBitmap);
            }

            if (imageDir.exists()) {
                FileOutputStream fos = new FileOutputStream(imageDir);
                // Use the compress method on the BitMap object to write image to the OutputStream
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Bitmap getCustomizedBackground() {
        ContextWrapper cw = new ContextWrapper(context);
        File directory = cw.getDir(FOLDER_PRIVATE_BACKGROUND, Context.MODE_PRIVATE);
        File imageDir = new File(directory, FOLDER_PRIVATE_BACKGROUND_NAME + ".png");

        try {
            return BitmapFactory.decodeStream(new FileInputStream(imageDir));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return null;
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
    }
}
